#include <string.h>
#include "PopupAbstractMessage.hpp"
#include "PopupSSL.hpp"

using namespace std;
using namespace Popup;

#define POPUP_BOOLEAN_TRUE  0x01
#define POPUP_BOOLEAN_FALSE 0x00

//=============================================================================
// Construction
//=============================================================================

AbstractMessage::AbstractMessage(MsgType p_id,
                                           MsgEncryption p_encryption) :
  type(p_id), encryption(p_encryption), payloadSize(0), freePayloadSize(0),
  message(0), fullMessage(0), valid(false), prepareDone(false)
{
}
//---------------------------------------------------------

AbstractMessage::~AbstractMessage()
{
  if (fullMessage != 0) {
    delete fullMessage;
    fullMessage = 0;
  }
}

//=============================================================================
// Payload allocation utilities
//=============================================================================

bool AbstractMessage::allocate(size_t p_nbBytes)
{
  // Payload is allocated by 256 bytes blocks
  size_t _allocatedSize = 256 * (((p_nbBytes - freePayloadSize) + 255) / 256);

  if (encryption != POPUP_ENCRYPTION_NONE)
  {
    // Encrypted size shall be a multiple of 8
    size_t encrytableSize = sizeof(MessageHeader) +
        payloadSize + freePayloadSize + _allocatedSize;
    if (encrytableSize % 8 != 0) {
      _allocatedSize += (8 - (encrytableSize % 8));
    }
  }

  // (Re)allocate memory
  size_t totalMsgSize = sizeof(RawMessageHeader) +
                        sizeof(MessageHeader) +
                        payloadSize + freePayloadSize + _allocatedSize;

  unsigned char *mem = (unsigned char *) realloc(fullMessage, totalMsgSize);

  if (mem != 0) {
    fullMessage = (RawMessage*) mem;
    message = &fullMessage->message;
    freePayloadSize += _allocatedSize;
    memset(&message->payload[payloadSize], 0, freePayloadSize);
    return true;
  }
  else {
    return false;
  }
}

//=============================================================================
// Send/Receive management
//=============================================================================

bool AbstractMessage::prepareSend()
{
  if (prepareDone == true)
  {
    return true;
  } else {
    if (onSend())
    {
      if ((message == 0) && !allocate(0)) {
        return false;
      } else {
        // Setup message header
        message->header.type        = type;
        message->header.payloadSize  = payloadSize;
        memset(&message->header.spare[0], 0, sizeof(message->header.spare));
        Message_hton(message);
        prepareDone = true;
        return true;
      }
    }
    else
    {
      return false;
    }
  }
}
//---------------------------------------------------------

bool AbstractMessage::deserialize(const Message *p_message)
{
  bool _rc = true;
  MessageField *_field = 0;
  size_t _index = 0;

  fields.clear();

  // Add a pointer to each field in fields map
  while ((_rc) && (_index < p_message->header.payloadSize)) {
    _field = (MessageField*) &p_message->payload[_index];
    MessageField_ntoh(_field);
      fields[_field->id] = _field;
    _index += sizeof(MessageField) + _field->size;
    //MessageField_dump(_field);
  }

  valid = (_rc && onReceive());

  return valid;
}

//=============================================================================
// Getters
//=============================================================================

#define POPUP_BLOWFISH_ALIGNMENT 8

size_t AbstractMessage::getUtilMessageSize() const
{
  if (encryption == POPUP_ENCRYPTION_BLOWFISH) {
    return POPUP_BLOWFISH_ALIGNMENT * (
        (sizeof(MessageHeader) + payloadSize +
            POPUP_BLOWFISH_ALIGNMENT - 1) / POPUP_BLOWFISH_ALIGNMENT);
  } else {
    return sizeof(MessageHeader) + payloadSize;
  }
}
//---------------------------------------------------------

size_t AbstractMessage::getTotalMessageSize() const
{
  return sizeof(RawMessageHeader) +
         sizeof(MessageHeader) +
         payloadSize;
}

//=============================================================================
// Payload management
//=============================================================================



bool AbstractMessage::payloadAddData(unsigned short p_fieldId,
                                          const void *p_fieldData,
                                          size_t p_fieldDataSize,
                                          unsigned short p_fieldDataType)
{
  bool _rc = true;
  size_t _requiredSize = sizeof(MessageField) + p_fieldDataSize;

  if (freePayloadSize < _requiredSize) {
    _rc = allocate(_requiredSize);
  }

  if (_rc) {
    MessageField *_field = (MessageField*) &message->payload[payloadSize];
    _field->id         = p_fieldId;
    _field->size       = p_fieldDataSize;
    _field->type       = p_fieldDataType;
    memcpy(_field->data, p_fieldData, p_fieldDataSize);
    MessageField_hton(_field);
    payloadSize     += _requiredSize;
    freePayloadSize -= _requiredSize;
  }

  return _rc;
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddOpaqueData(unsigned short p_fieldId,
                                                size_t p_fieldDataSize,
                                                void **p_buffer)
{
  bool _rc = true;
  size_t _requiredSize = sizeof(MessageField) + p_fieldDataSize;

  if (freePayloadSize < _requiredSize) {
    _rc = allocate(_requiredSize);
  }

  if (_rc) {
    MessageField *_field = (MessageField*) &message->payload[payloadSize];
    _field->id         = p_fieldId;
    _field->size       = p_fieldDataSize;
    _field->type       = POPUP_FIELD_DATA_TYPE_UNK;
    *p_buffer = &_field->data[0];
    MessageField_hton(_field);
    payloadSize     += _requiredSize;
    freePayloadSize -= _requiredSize;
  }

  return _rc;
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddFileContent(unsigned short p_fieldId,
                                                 const string & p_path)
{
  bool _rc = true;
  size_t _filesize = PopupUtils::getFileSize(p_path);
  if (_filesize == 0) {
    _rc = false;
  }
  else
  {
    size_t _requiredSize = sizeof(MessageField) + _filesize;

    if (freePayloadSize < _requiredSize) {
      _rc = allocate(_requiredSize);
    }

    if (_rc) {
      MessageField *_field = (MessageField*) &message->payload[payloadSize];
      _field->id         = p_fieldId;
      _field->size       = _filesize;
      _field->type       = POPUP_FIELD_DATA_TYPE_UNK;
      memset(&_field->data[0], 0xCA, _filesize);
      _rc = PopupUtils::dumpFileToBuffer(&_field->data[0], _filesize, p_path);
      MessageField_hton(_field);
      payloadSize     += _requiredSize;
      freePayloadSize -= _requiredSize;
    }
  }

  return _rc;
}
//---------------------------------------------------------

const MessageField *AbstractMessage::payloadGetField(
    unsigned short p_fieldId) const
{
  FieldsMap::const_iterator it = fields.find(p_fieldId);
  if (it != fields.end()) {
    return it->second;
  } else {
    return 0;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddString(unsigned short p_fieldId,
                                       const string & p_value)
{
  return payloadAddData(p_fieldId, p_value.c_str(), p_value.length() + 1,
                        POPUP_FIELD_DATA_TYPE_STRING);
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddString(unsigned short p_fieldId,
                                            const vector<string> & p_values)
{
  unsigned int _size = 0;
  vector<string>::const_iterator _it;
  for (_it = p_values.begin(); _it != p_values.end(); _it++) {
    _size += _it->length() + 1;
  }
  // Allocate a buffer to store all strings content
  unsigned char *_buffer = new unsigned char[_size];

  // Copy all strings to this buffer
  size_t _pos = 0;
  for (_it = p_values.begin(); _it != p_values.end(); _it++) {
    memcpy(&_buffer[_pos], _it->c_str(), _it->length() + 1);
    _pos += _it->length() + 1;
  }

  // Add resulting field as raw data
  bool _rc = payloadAddData(p_fieldId, _buffer, _size, POPUP_FIELD_DATA_TYPE_UNK);

  delete _buffer;

  return _rc;
}
//----------------------1-----------------------------------

bool AbstractMessage::payloadGetString(unsigned short p_fieldId,
                                            string & p_value) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    p_value.assign((const char*) _field->data);
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetString(unsigned short p_fieldId,
                                            vector<string> & p_values) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    unsigned int _index = 0;
    while (_index < _field->size) {
      unsigned int _length = PopupUtils::strnlen(
          (const char*) &_field->data[_index],
          (_field->size - _index));
      string _value;
      _value.assign((const char*) &_field->data[_index]);
      _index += (_length+1);
      p_values.push_back(_value);
    }
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddBoolean(unsigned short p_fieldId,
                                             bool p_value)
{
  unsigned char _value = (p_value? POPUP_BOOLEAN_TRUE : POPUP_BOOLEAN_FALSE);
  return payloadAddData(p_fieldId, &_value, sizeof(unsigned char),
                        POPUP_FIELD_DATA_TYPE_BOOLEAN);
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetBoolean(unsigned short p_fieldId,
                                             bool & p_value) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    unsigned char *_value = (unsigned char*) _field->data;
    p_value = (*_value == POPUP_BOOLEAN_TRUE);
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddShort(unsigned short p_fieldId,
                                           unsigned short p_value)
{
  unsigned short _value = p_value;
  return payloadAddData(p_fieldId, &_value, sizeof(unsigned short),
                        POPUP_FIELD_DATA_TYPE_SHORT);
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddShort(unsigned short p_fieldId,
                                      const vector<unsigned short> & p_values)
{
  unsigned short _values[p_values.size()];
  vector<unsigned short>::const_iterator _it;
  size_t _n;
  for (_n = 0, _it = p_values.begin(); _it != p_values.end(); _n++, _it++) {
    _values[_n] = *_it;
  }
  return payloadAddData(p_fieldId, _values, _n * sizeof(unsigned short),
                        POPUP_FIELD_DATA_TYPE_SHORT);
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetShort(unsigned short p_fieldId,
                                      unsigned short & p_value) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    unsigned short * _value = (unsigned short*) _field->data;
    p_value = *_value;
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetShort(unsigned short p_fieldId,
                                      vector<unsigned short> & p_values) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    size_t _nbValues = _field->size / sizeof(unsigned short);
    for (size_t _n = 0; _n < _nbValues; _n++) {
      p_values.push_back(((unsigned short*) _field->data)[_n]);
    }
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddLong(unsigned short p_fieldId,
                                          unsigned int p_value)
{
  unsigned int _value = p_value;
  return payloadAddData(p_fieldId, &_value, sizeof(unsigned int),
                        POPUP_FIELD_DATA_TYPE_LONG);
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddLong(unsigned short p_fieldId,
                                          const set<unsigned int> & p_values)
{
  unsigned int _values[p_values.size()];
  set<unsigned int>::const_iterator _it;
  size_t _n;
  for (_n = 0, _it = p_values.begin(); _it != p_values.end(); _n++, _it++) {
    _values[_n] = *_it;
  }
  return payloadAddData(p_fieldId, _values, _n * sizeof(unsigned int),
                        POPUP_FIELD_DATA_TYPE_LONG);
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetLong(unsigned short p_fieldId,
                                          unsigned int & p_value) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    unsigned int * _value = (unsigned int*) _field->data;
    p_value = *_value;
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetLong(unsigned short p_fieldId,
                                          set<unsigned int> & p_values) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    size_t _nbValues = _field->size / sizeof(unsigned int);
    for (size_t _n = 0; _n < _nbValues; _n++) {
      p_values.insert(((unsigned int*) _field->data)[_n]);
    }
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddLongLong(unsigned short p_fieldId,
                                         unsigned long long p_value)
{
  unsigned long long _value = p_value;
  return payloadAddData(p_fieldId, &_value, sizeof(unsigned long long),
                        POPUP_FIELD_DATA_TYPE_LONG_LONG);
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddLongLong(unsigned short p_fieldId,
                                         const set<unsigned long long> & p_values)
{
  unsigned long long _values[p_values.size()];
  set<unsigned long long>::const_iterator _it;
  size_t _n;
  for (_n = 0, _it = p_values.begin(); _it != p_values.end(); _n++, _it++) {
    _values[_n] = *_it;
  }
  return payloadAddData(p_fieldId, _values, _n * sizeof(unsigned long long),
                        POPUP_FIELD_DATA_TYPE_LONG_LONG);
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetLongLong(unsigned short p_fieldId,
                                          unsigned long long & p_value) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    unsigned long long * _value = (unsigned long long*) _field->data;
    p_value = *_value;
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadGetLongLong(unsigned short p_fieldId,
                                          set<unsigned long long> & p_values) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    size_t _nbValues = _field->size / sizeof(unsigned long long);
    for (size_t _n = 0; _n < _nbValues; _n++) {
      p_values.insert(((unsigned long long*) _field->data)[_n]);
    }
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadDumpFieldToFile(unsigned short p_fieldId,
                                                  const string & p_path) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    return PopupUtils::dumpBufferToFile(p_path, _field->data, _field->size);
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadDumpFieldToFile(unsigned short p_fieldId,
                                             PopupUtils::FileStream *p_file) const
{
  const MessageField *_field = payloadGetField(p_fieldId);
  if (_field == 0) {
    return false;
  } else {
    size_t _totalWritten = PopupUtils::fillStreamFromBuffer(
        p_file, &_field->data[0], _field->size);
    if (_totalWritten != _field->size) {
      return false;
    }
    return true;
  }
}
//---------------------------------------------------------

bool AbstractMessage::payloadAddFromFile(unsigned short p_fieldId,
                                         PopupUtils::FileStream *p_file,
                                         unsigned int p_fieldDataSize)
{
  bool _rc = true;
  size_t _requiredSize = sizeof(MessageField) + p_fieldDataSize;

  if (freePayloadSize < _requiredSize) {
    _rc = allocate(_requiredSize);
  }

  if (_rc) {
    MessageField *_field = (MessageField*) &message->payload[payloadSize];
    _field->id         = p_fieldId;
    _field->size       = p_fieldDataSize;
    _field->type       = POPUP_FIELD_DATA_TYPE_UNK;
    size_t _nbRead = PopupUtils::fillBufferFromStream(
        &_field->data[0], p_fieldDataSize, p_file);
    if (_nbRead != p_fieldDataSize) {
      _rc = false;
    }
    MessageField_hton(_field);
    payloadSize     += _requiredSize;
    freePayloadSize -= _requiredSize;
  }

  return _rc;
}
